//  MetricsUtil.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package nsga.comparison.util;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.*;

/**
 * This class provides some utilities to compute quality indicators.
 **/
public class MetricsUtil {

	/**
	 * This method reads a Pareto Front for a file.
	 * 
	 * @param path
	 *            The path to the file that contains the pareto front
	 * @return double [][] whit the pareto front
	 **/
	public double[][] readFront(String path) {
		try {
			// Open the file
			FileInputStream fis = new FileInputStream(path);
			InputStreamReader isr = new InputStreamReader(fis);
			BufferedReader br = new BufferedReader(isr);

			List<double[]> list = new ArrayList<double[]>();
			int numberOfObjectives = 0;
			String aux = br.readLine();
			while (aux != null) {
				StringTokenizer st = new StringTokenizer(aux);
				int i = 0;
				numberOfObjectives = st.countTokens();
				double[] vector = new double[st.countTokens()];
				while (st.hasMoreTokens()) {
					double value = new Double(st.nextToken());
					vector[i] = value;
					i++;
				}
				list.add(vector);
				aux = br.readLine();
			}

			br.close();

			double[][] front = new double[list.size()][numberOfObjectives];
			for (int i = 0; i < list.size(); i++) {
				front[i] = list.get(i);
			}
			return front;

		} catch (Exception e) {
			System.out.println("InputFacilities crashed reading for file: " + path);
			e.printStackTrace();
		}
		return null;
	} // readFront

	/**
	 * Gets the maximum values for each objectives in a given pareto front
	 * 
	 * @param front
	 *            The pareto front
	 * @param noObjectives
	 *            Number of objectives in the pareto front
	 * @return double [] An array of noOjectives values whit the maximun values
	 *         for each objective
	 **/
	public double[] getMaximumValues(double[][] front, int noObjectives) {
		double[] maximumValue = new double[noObjectives];
		for (int i = 0; i < noObjectives; i++)
			maximumValue[i] = Double.NEGATIVE_INFINITY;

		for (double[] aFront : front) {
			for (int j = 0; j < aFront.length; j++) {
				if (aFront[j] > maximumValue[j])
					maximumValue[j] = aFront[j];
			}
		}

		return maximumValue;
	} // getMaximumValues

	/**
	 * Gets the minimum values for each objectives in a given pareto front
	 * 
	 * @param front
	 *            The pareto front
	 * @param noObjectives
	 *            Number of objectives in the pareto front
	 * @return double [] An array of noOjectives values whit the minimum values
	 *         for each objective
	 **/
	public double[] getMinimumValues(double[][] front, int noObjectives) {
		double[] minimumValue = new double[noObjectives];
		for (int i = 0; i < noObjectives; i++)
			minimumValue[i] = Double.MAX_VALUE;

		for (double[] aFront : front) {
			for (int j = 0; j < aFront.length; j++) {
				if (aFront[j] < minimumValue[j])
					minimumValue[j] = aFront[j];
			}
		}
		return minimumValue;
	} // getMinimumValues

	/**
	 * This method returns the distance (taken the euclidean distance) between
	 * two points given as <code>double []</code>
	 * 
	 * @param a
	 *            A point
	 * @param b
	 *            A point
	 * @return The euclidean distance between the points
	 **/
	public double distance(double[] a, double[] b) {
		double distance = 0.0;

		for (int i = 0; i < a.length; i++) {
			distance += Math.pow(a[i] - b[i], 2.0);
		}
		return Math.sqrt(distance);
	} // distance

	/**
	 * Gets the distance between a point and the nearest one in a given front
	 * (the front is given as <code>double [][]</code>)
	 * 
	 * @param point
	 *            The point
	 * @param front
	 *            The front that contains the other points to calculate the
	 *            distances
	 * @return The minimun distance between the point and the front
	 **/
	public double distanceToClosedPoint(double[] point, double[][] front) {
		double minDistance = distance(point, front[0]);

		for (int i = 1; i < front.length; i++) {
			double aux = distance(point, front[i]);
			if (aux < minDistance) {
				minDistance = aux;
			}
		}

		return minDistance;
	} // distanceToClosedPoint

	/**
	 * Gets the distance between a point and the nearest one in a given front,
	 * and this distance is greater than 0.0
	 * 
	 * @param point
	 *            The point
	 * @param front
	 *            The front that contains the other points to calculate the
	 *            distances
	 * @return The minimun distances greater than zero between the point and the
	 *         front
	 */
	public double distanceToNearestPoint(double[] point, double[][] front) {
		double minDistance = Double.MAX_VALUE;

		for (double[] aFront : front) {
			double aux = distance(point, aFront);
			if ((aux < minDistance) && (aux > 0.0)) {
				minDistance = aux;
			}
		}

		return minDistance;
	} // distanceToNearestPoint

	/**
	 * This method receives a pareto front and two points, one whit maximum
	 * values and the other with minimum values allowed, and returns a the
	 * normalized Pareto front.
	 * 
	 * @param front
	 *            A pareto front.
	 * @param maximumValue
	 *            The maximum values allowed
	 * @param minimumValue
	 *            The minimum values allowed
	 * @return the normalized pareto front
	 **/
	public double[][] getNormalizedFront(double[][] front, double[] maximumValue, double[] minimumValue) {

		double[][] normalizedFront = new double[front.length][];

		for (int i = 0; i < front.length; i++) {
			normalizedFront[i] = new double[front[i].length];
			for (int j = 0; j < front[i].length; j++) {
				normalizedFront[i][j] = (front[i][j] - minimumValue[j]) / (maximumValue[j] - minimumValue[j]);
			}
		}
		return normalizedFront;
	} // getNormalizedFront

	/**
	 * This method receives a normalized pareto front and return the inverted
	 * one. This operation needed for minimization problems
	 * 
	 * @param front
	 *            The pareto front to inverse
	 * @return The inverted pareto front
	 **/
	public double[][] invertedFront(double[][] front) {
		double[][] invertedFront = new double[front.length][];

		for (int i = 0; i < front.length; i++) {
			invertedFront[i] = new double[front[i].length];
			for (int j = 0; j < front[i].length; j++) {
				if (front[i][j] <= 1.0 && front[i][j] >= 0.0) {
					invertedFront[i][j] = 1.0 - front[i][j];
				} else if (front[i][j] > 1.0) {
					invertedFront[i][j] = 0.0;
				} else if (front[i][j] < 0.0) {
					invertedFront[i][j] = 1.0;
				}
			}
		}
		return invertedFront;
	} // invertedFront

	/**
	 * 将Pareto前沿写入到文件（各目标值以空格分隔，一行对应一个解的目标值序列）
	 * 
	 * @param front:Pareto前沿
	 * @param frontFile：写入文件的名称
	 */
	public void writeToFile(ArrayList<ArrayList<Float>> front, String frontFile) {
		FileOutputStream fos;
		int size = front.size();
		if (size == 0) {
			return;
		}
		int objNum = front.get(0).size();
		try {
			fos = new FileOutputStream(frontFile);
			PrintWriter os = new PrintWriter(fos);

			for (ArrayList<Float> sln : front) {
				String rowStr = "";
				for (int i = 0; i < objNum; i++) {
//					if ((i == 0) || (i == objNum - 1)) {
					if (i == 0) {
						rowStr += String.valueOf(sln.get(i));
					} else {
						rowStr += " " + String.valueOf(sln.get(i));
					}

				}
				os.println(rowStr);
			}
			os.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 一组目标值
	 * @param fronts
	 * @return
	 */
	public ArrayList<ArrayList<Integer>> nondominatedSort(ArrayList<ArrayList<Float>> fronts) {
		ArrayList<ArrayList<Integer>> idxss = new ArrayList<ArrayList<Integer>>();
		ArrayList<Integer> rnk0Idxs = new ArrayList<Integer>();// 等级0（最好）的染色体下标
		ArrayList<DominateInfo> dominateInfos = new ArrayList<DominateInfo>();
		int size = fronts.size();
		for (int i = 0; i < size; i++) {		
			ArrayList<Float> oneObj1 = fronts.get(i);
			DominateInfo dominateInfo = new DominateInfo();
			dominateInfo.idx = i;
			float resTime1 = oneObj1.get(0);		
			float reltVal1 = oneObj1.get(1);
//			float costVal1 = oneObj1.get(2);	
			for (int j = 0; j < size; j++) {
				ArrayList<Float> oneObj2 = fronts.get(j);
//				if (!isEqualList(oneObj1, oneObj2)) {
				if (j!=i) {
					float resTime2 = oneObj2.get(0);
					float reltVal2 = oneObj2.get(1);
//					float costVal2 = oneObj2.get(2);
	
//					if ((Float.compare(resTime2, resTime1) <= 0 &&  (Float.compare(costVal2, costVal1) < 0)&&(Float.compare(reltVal2, reltVal1) <= 0))// chrom2比chrom1优
//							|| (Float.compare(resTime2, resTime1) <= 0 && (Float.compare(costVal2, costVal1) <= 0)&&(Float.compare(reltVal2, reltVal1) < 0))
//							|| (Float.compare(resTime2, resTime1) < 0 && (Float.compare(costVal2, costVal1) <= 0)&&(Float.compare(reltVal2, reltVal1) <= 0))) {
//						dominateInfo.superiorNum += 1;
//					}
//
//					if ((Float.compare(resTime1, resTime2) <= 0 && (Float.compare(costVal1, costVal2) < 0)&&(Float.compare(reltVal1, reltVal2) <= 0))// chrom2比chrom1劣
//							|| (Float.compare(resTime1, resTime2) <= 0 && (Float.compare(costVal1, costVal2) <= 0)&&(Float.compare(reltVal1, reltVal2) < 0))
//							|| (Float.compare(resTime1, resTime2) < 0 && (Float.compare(costVal1, costVal2) <= 0)&&(Float.compare(reltVal1, reltVal2) <= 0))) {
//						dominateInfo.inferiorIdxs.add(new Integer(j));
//					}
					if ((Float.compare(resTime2, resTime1) <= 0 &&(Float.compare(reltVal2, reltVal1) <= 0))// chrom2比chrom1优
							|| (Float.compare(resTime2, resTime1) < 0 &&(Float.compare(reltVal2, reltVal1) <= 0))) {
						dominateInfo.superiorNum += 1;
					}

					if ((Float.compare(resTime1, resTime2) <= 0 &&(Float.compare(reltVal1, reltVal2) <= 0))// chrom2比chrom1劣
							|| (Float.compare(resTime1, resTime2) < 0  &&(Float.compare(reltVal1, reltVal2) <= 0))) {
						dominateInfo.inferiorIdxs.add(new Integer(j));
					}

				}
			}
			if (dominateInfo.superiorNum == 0) {
				rnk0Idxs.add(i);
			}
			dominateInfos.add(dominateInfo);
		}
		if (rnk0Idxs.size() > 0) {
			idxss.add(rnk0Idxs);
		}

		ArrayList<Integer> curRnkIdxs = rnk0Idxs;
		while (curRnkIdxs.size() > 0) {
			ArrayList<Integer> nxtRnkIdxs = new ArrayList<Integer>();
			for (Integer idx : curRnkIdxs) {
				DominateInfo curInfo = dominateInfos.get(idx);
				for (Integer inferiorIdx : curInfo.inferiorIdxs) {
					DominateInfo inferiorInfo = dominateInfos.get(inferiorIdx);
					inferiorInfo.superiorNum -= 1;
					if (inferiorInfo.superiorNum == 0) {
						nxtRnkIdxs.add(inferiorInfo.idx);
					}
				}
			}
			if (nxtRnkIdxs.size() > 0) {
				idxss.add(nxtRnkIdxs);
			}
			curRnkIdxs = nxtRnkIdxs;
		}
		return idxss;
	}
	
	/**
	 * 一组目标值
	 * @param fronts
	 * @return
	 */
//	public ArrayList<ArrayList<Integer>> nondominatedSort13(ArrayList<ArrayList<Float>> fronts) {
//		ArrayList<ArrayList<Integer>> idxss = new ArrayList<ArrayList<Integer>>();
//		ArrayList<Integer> rnk0Idxs = new ArrayList<Integer>();// 等级0（最好）的染色体下标
//		ArrayList<DominateInfo> dominateInfos = new ArrayList<DominateInfo>();
//		int size = fronts.size();
//		for (int i = 0; i < size; i++) {
//			ArrayList<Float> oneObj1 = fronts.get(i);
//			DominateInfo dominateInfo = new DominateInfo();
//			dominateInfo.idx = i;
//			float resTime1 = oneObj1.get(0);		
////			float reltVal1 = oneObj1.get(1);
//			float costVal1 = oneObj1.get(2);	
//			for (int j = 0; j < size; j++) {
//				ArrayList<Float> oneObj2 = fronts.get(j);
////				if (!isEqualList(oneObj1, oneObj2)) {
//				if (j!=i) {
//					float resTime2 = oneObj2.get(0);
////					float reltVal2 = oneObj2.get(1);
//					float costVal2 = oneObj2.get(2);
//	
//					if ((Float.compare(resTime2, resTime1) <= 0 &&  (Float.compare(costVal2, costVal1) < 0))// chrom2比chrom1优
//							|| (Float.compare(resTime2, resTime1) <= 0 && (Float.compare(costVal2, costVal1) <= 0))
//							|| (Float.compare(resTime2, resTime1) < 0 && (Float.compare(costVal2, costVal1) <= 0))) {
//						dominateInfo.superiorNum += 1;
//					}
//
//					if ((Float.compare(resTime1, resTime2) <= 0 && (Float.compare(costVal1, costVal2) < 0))// chrom2比chrom1劣
//							|| (Float.compare(resTime1, resTime2) <= 0 && (Float.compare(costVal1, costVal2) <= 0))
//							|| (Float.compare(resTime1, resTime2) < 0 && (Float.compare(costVal1, costVal2) <= 0))) {
//						dominateInfo.inferiorIdxs.add(new Integer(j));
//					}
//
//				}
//			}
//			if (dominateInfo.superiorNum == 0) {
//				rnk0Idxs.add(i);
//			}
//			dominateInfos.add(dominateInfo);
//		}
//		if (rnk0Idxs.size() > 0) {
//			idxss.add(rnk0Idxs);
//		}
//
//		ArrayList<Integer> curRnkIdxs = rnk0Idxs;
//		while (curRnkIdxs.size() > 0) {
//			ArrayList<Integer> nxtRnkIdxs = new ArrayList<Integer>();
//			for (Integer idx : curRnkIdxs) {
//				DominateInfo curInfo = dominateInfos.get(idx);
//				for (Integer inferiorIdx : curInfo.inferiorIdxs) {
//					DominateInfo inferiorInfo = dominateInfos.get(inferiorIdx);
//					inferiorInfo.superiorNum -= 1;
//					if (inferiorInfo.superiorNum == 0) {
//						nxtRnkIdxs.add(inferiorInfo.idx);
//					}
//				}
//			}
//			if (nxtRnkIdxs.size() > 0) {
//				idxss.add(nxtRnkIdxs);
//			}
//			curRnkIdxs = nxtRnkIdxs;
//		}
//		return idxss;
//	}
	
	/**
	 * 支配信息
	 * @author nyc
	 *
	 */
	protected class DominateInfo {
		int idx = -1;// 本个体在种群中的下标
		int superiorNum = 0;// 比本个体优的个体数目
		List<Integer> inferiorIdxs = new ArrayList<Integer>();// 比本个体差的个体的下标

	}
	
	/**
	 * 判断两个两个解的支配情况
	 * @param sln1
	 * @param sln2
	 * @return:1表示sln1优于sln2;-1表示sln2优于sln1;
	 * 			0表示sln1和sln2相等；2表示sln1和sln2不可比
	 */	
	public int isDominate(double[] sln1, double[] sln2) {
		int rlt = 0;
		BigDecimal data10 = new BigDecimal(sln1[0]);  
		BigDecimal data11 = new BigDecimal(sln1[1]);  
//		BigDecimal data12 = new BigDecimal(sln1[2]);
		
		BigDecimal data20 = new BigDecimal(sln2[0]);  
		BigDecimal data21 = new BigDecimal(sln2[1]);  
//		BigDecimal data22 = new BigDecimal(sln2[2]);

		if ((data10.compareTo(data20)<=0 && data11.compareTo(data21)<=0
				)
				|| (data10.compareTo(data20)<=0 && data11.compareTo(data21)<0
						)
				|| (data10.compareTo(data20)<0 && data11.compareTo(data21)<=0
						)) {
			rlt = 1;// 1表示sln1优于sln2
		} else if ((data10.compareTo(data20)>=0 && data11.compareTo(data21)>=0
				)
				|| (data10.compareTo(data20)>=0 && data11.compareTo(data21)>0
						)
				|| (data10.compareTo(data20)>0 && data11.compareTo(data21)>=0
						)) {
			rlt = -1;// -1表示sln2优于sln1
		} else if (data10.compareTo(data20)==0 && data11.compareTo(data21)==0
				) {
			rlt = 0;// 0表示sln1和sln2相等
		} else {
			rlt = 2;// 2表示sln1和sln2不可比
		}
		return rlt;
	}
	
	/**
	 * 根据一个从数据库读来的最后一代种群的目标值，计算front
	 * @param objValss
	 * @return
	 */
	public ArrayList<ArrayList<Float>> getFrontByObjvals(ArrayList<ArrayList<Float>> objValss){
		delRepChrom(objValss);
		ArrayList<ArrayList<Float>> front = new ArrayList<ArrayList<Float>>();
		ArrayList<ArrayList<Integer>> idxss = nondominatedSort(objValss);
		ArrayList<Integer> idxs = idxss.get(0);
		for (Integer idx : idxs) {
			front.add(objValss.get(idx));				
		}
		return front;
	}
	
	public void delRepChrom(ArrayList<ArrayList<Float>> objValss) {
		for (int i = 0; i < objValss.size(); i++) {
			ArrayList<Float> chrom1 = objValss.get(i);
			for (int j = i + 1; j < objValss.size(); j++) {
				ArrayList<Float> chrom2 = objValss.get(j);
				if (isEqual(chrom1,chrom2)) {
					objValss.remove(j);
					j = j - 1;
				}
			}
		}
	}
	
	public boolean isEqual(ArrayList<Float> chrom1,ArrayList<Float> chrom2) {
		boolean retVal = false;
		
		float srcObjVal1 = chrom1.get(0);
		float srcObjVal2 = chrom1.get(1);
//		float srcObjVal3 = chrom1.get(2);

		float targetObjVal1 = chrom2.get(0);
		float targetObjVal2 = chrom2.get(1);
//		float targetObjVal3 = chrom2.get(2);

		boolean obj1IsEqual = Float.compare(srcObjVal1, targetObjVal1) == 0;
		boolean obj2IsEqual = Float.compare(srcObjVal2, targetObjVal2) == 0;
//		boolean obj3IsEqual = Float.compare(srcObjVal3, targetObjVal3) == 0;
		//目标值是否相同
		boolean objEqual = obj1IsEqual && obj2IsEqual ;
		//个体是否相同
		if (objEqual) {
			retVal = true;
		}
		return retVal;
	}
	
//	/**
//	 * 
//	 * @param front1:算法1求出的Pareto前沿1
//	 * @param front2:算法2求出的Pareto前沿2
//	 * @param refFront:前沿1和2合并后得到的前沿
//	 */
//	public ArrayList<ArrayList<Float>> getCombiFront(ArrayList<ArrayList<Float>> front1, ArrayList<ArrayList<Float>> front2) {
//
//		ArrayList<ArrayList<Float>> combiFront=new ArrayList<ArrayList<Float>>();
//
//		ArrayList<ArrayList<Float>> temp=new ArrayList<ArrayList<Float>>();
//		for(ArrayList<Float> sln1:front1){
//			temp.add(sln1);
//		}
//		for(ArrayList<Float>sln2:front2){
//			temp.add(sln2);
//		}
//		
//		ArrayList<ArrayList<Integer>> idxss = nondominatedSort(temp);
//		ArrayList<Integer> idxs = idxss.get(0);
//		for (Integer idx : idxs) {
//			combiFront.add(temp.get(idx));				
//		}
//	
//		return combiFront;
//
//	}

} // MetricsUtil
